#include "mkvmuxfilter.hpp"
#include <uuids.h>
#include <dvdmedia.h>
#include "mkvstreamvideompeg2.hpp"
//#include "odbgstream.hpp"
//using std::endl;
//using std::boolalpha;

namespace MkvMux
{

InpinVideo::InpinVideo(Filter* p) :
    Inpin(p, L"video")
{
    AM_MEDIA_TYPE mt;
    
    mt.majortype = MEDIATYPE_Video;
    mt.subtype = MEDIASUBTYPE_MPEG2_VIDEO;  //TODO: more subtypes here
    
    mt.bFixedSizeSamples = FALSE;
    mt.bTemporalCompression = FALSE;
    mt.lSampleSize = 0;
    mt.formattype = GUID_NULL;
    mt.pUnk = 0;
    mt.cbFormat = 0;
    mt.pbFormat = 0;
    
    m_preferred.Add(mt);
}


InpinVideo::~InpinVideo()
{
}


HRESULT InpinVideo::QueryAccept(const AM_MEDIA_TYPE* pmt)
{
    if (pmt == 0)
        return E_INVALIDARG;
        
    const AM_MEDIA_TYPE& mt = *pmt;
    
    if (mt.majortype != MEDIATYPE_Video)
        return S_FALSE;
    
    if (mt.subtype != MEDIASUBTYPE_MPEG2_VIDEO)
        return S_FALSE;
        
    if (mt.formattype != FORMAT_MPEG2_VIDEO)
        return S_FALSE;

    //if (mt.cbFormat < sizeof(MPEG2VIDEOINFO))
    //    return S_FALSE;
        
    if (mt.cbFormat < (ULONG)FIELD_OFFSET(MPEG2VIDEOINFO, dwSequenceHeader[0]))
        return S_FALSE;

    if (mt.pbFormat == 0)
        return S_FALSE;
        
    const MPEG2VIDEOINFO& mpg = (MPEG2VIDEOINFO&)(*mt.pbFormat);

    if (mt.cbFormat < SIZE_MPEG2VIDEOINFO(&mpg))
        return S_FALSE;

    const VIDEOINFOHEADER2& vih = mpg.hdr;
    const BITMAPINFOHEADER& bmih = vih.bmiHeader;
    bmih;
    
    //TODO: anything else?
    //we have seqhdr
    
    return S_OK;  
}


HRESULT InpinVideo::GetAllocatorRequirements(ALLOCATOR_PROPERTIES* p)
{
    if (p == 0)
        return E_POINTER;
        
    ALLOCATOR_PROPERTIES& props = *p;
    
    //TODO: estimate this value based on value of framerate.
    props.cBuffers = 2;  //we copy payload immediately    
    props.cbBuffer = 0;
    props.cbAlign = 0;
    props.cbPrefix = 0;
    
    return S_OK;
}


HRESULT InpinVideo::OnInit()
{  
   assert(m_connection);
   assert(!m_connection_mt.Empty());

   const AM_MEDIA_TYPE& mt = m_connection_mt[0];
   assert(mt.majortype == MEDIATYPE_Video);
   assert(mt.subtype == MEDIASUBTYPE_MPEG2_VIDEO);  //TODO: more subtypes here
   assert(mt.cbFormat >= sizeof(VIDEOINFOHEADER2));
   assert(mt.pbFormat);  //fully vetted in InpinVideo::QueryAccept

   const MPEG2VIDEOINFO& mpg = (MPEG2VIDEOINFO&)(*mt.pbFormat);
   assert(mt.cbFormat >= SIZE_MPEG2VIDEOINFO(&mpg));

   const DWORD cb = mpg.cbSequenceHeader;
   assert(cb >= 4);

   const VIDEOINFOHEADER2& vih = mpg.hdr;
   const BITMAPINFOHEADER& bmih = vih.bmiHeader;
   bmih;

   //http://dvd.sourceforge.net/dvdinfo/mpeghdrs.html#seq

   const BYTE* const h = MPEG2_SEQUENCE_INFO(&mpg);
   assert(h[0] == 0);
   assert(h[1] == 0);
   assert(h[2] == 1);
   assert(h[3] == 0xB3);
   
   MkvContext& mkv = m_pFilter->m_mkv;

   m_pStream = new (std::nothrow) MkvStreamVideoMpeg2(mkv, h, cb);
   assert(m_pStream);  //TODO

   mkv.SetVideo(m_pStream);   

   return S_OK;
}


void InpinVideo::OnFinal()
{
   MkvContext& ctx = m_pFilter->m_mkv;
   ctx.SetVideo(0);
}


#if 0
HRESULT InpinVideo::OnReceive(
    IMediaSample* pSample,
    Lock& lock)
{   
    //We hold the filter lock on entry to this subprogram.
    
    HRESULT hr = m_pStream->Write(pSample);
    assert(hr == S_OK);  //TODO
    
    const BOOL b = SetEvent(m_hSample);  //notify other pin
    assert(b);
    
    if (!m_pStream->Wait())
        return S_OK;
        
    //TODO:
    //If pStream->Wait returns true, it implies that there's
    //another stream.  However, even without another pin
    //(that is, pStream->Wait returns false), we must still
    //wait if we're paused. 
    
    //If video stream is ahead of audio, then wait for audio.
    //Audio stream will signal when new sample is delivered.
    
    //If there's no audio stream connection, then don't wait.
    
    //If audio stream is EOS, then don't wait.
    
    //While we're waiting, we must wake up because:
    //  receipt of audio sample
    //  flush
    //  transition to stopped
    //  any other transition?
    
    //We must signal receipt of this video sample, so audio
    //streaming thread will wake up.
    
    //How do we detect whether video stream is ahead of audio?
    
    //If we wait, we must release the filter lock.
    
    //If we transition to stopped, then wake up and release caller.
    
    //If we're paused, then write frame, and block caller.
    //If we transition from paused, then wake up and release caller.
    
    const HANDLE hAudio = m_pFilter->m_inpin_audio.m_hSample;
    
    enum { cHandles = 2 };
    const HANDLE hh[cHandles] = { m_hStateChangeOrFlush, hAudio };
    
    for (;;)
    {
        lock.Release();
    
        DWORD index;
            
        hr = CoWaitForMultipleHandles(
                0,  //wait flags
                INFINITE,
                cHandles,
                hh,
                &index);
                
        assert(hr == S_OK);
        
        if (index == 0)  //hStateChangeOrFlush
            return S_OK;
            
        assert(index == 1);  //hAudio
        
        lock.Seize(m_pFilter);
        
        if (!m_pStream->Wait())
            return S_OK;
    }
}
#endif


//HRESULT InpinVideo::OnEndOfStream()
//{
//    return m_pFilter->m_outpin.WriteVideoEOS();
//}


HANDLE InpinVideo::GetOtherHandle() const
{
    const InpinAudio& ia = m_pFilter->m_inpin_audio;    
    return ia.m_hSample;
}



} //end namespace MkvMux
